﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barebones.Components;
using Barebones.Dependencies;
using Meat.Resources;
using Microsoft.Xna.Framework.Graphics;
using Barebones.Framework;
using Microsoft.Xna.Framework;
using Barebones.Xna;

namespace Meat.Rendering
{
    /// <summary>
    /// This is a dead simple renderer only cappable of rendering meshes with textures and models
    /// This is not meant to be useful for a real project, only as a means of getting started quickly
    /// </summary>
    public class RawRenderer : EngineComponent, Barebones.Framework.IDrawable
    {
        RenderSettings m_DefaultRenderSettings = new RenderSettings() { Lighting = true } ;

        BasicEffect m_MeshEffect;
        GraphicsDevice m_Device;

        Matrix m_Projection;

        List<Handle<Model>> m_Models = new List<Handle<Model>>();

        List<AbstractHandle> m_Meshes = new List<AbstractHandle>();

        public Color ClearColor
        { get; set; }

        public Camera Camera
        { get; set; }

        public Matrix Projection
        { get { return m_Projection; } }

        public RawRenderer(Matrix projection)
            : this(projection, Color.White)
        {}

        public RawRenderer(Matrix projection, Color clear_color)
        {
            ClearColor = clear_color;
            m_Projection = projection;
        }

        protected override void OnOwnerSet()
        {
            Owner.Forum.RegisterListener<InitializeMessage>(OnInitialize);
            Owner.Forum.RegisterListener<CameraSetMsg>(item => Camera = item.Camera);

            base.OnOwnerSet();
        }

        void OnInitialize(InitializeMessage msg)
        {
            m_Device = msg.Device;

            m_MeshEffect = new BasicEffect(m_Device);

            m_MeshEffect.TextureEnabled = true;
            m_MeshEffect.VertexColorEnabled = false;
            m_MeshEffect.FogEnabled = false;
            m_MeshEffect.LightingEnabled = false;

            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            m_Device.RasterizerState = rasterizerState;

            m_Device.BlendState = BlendState.AlphaBlend;
        }

        public override IEnumerable<IDependency> GetDependencies()
        {
            yield return new Dependency<Handle<Model>>(item => m_Models.Add(item), item => m_Models.Remove(item));
            yield return new Dependency<Handle<VertexBufferResource<VertexPositionNormalTexture>>>(item => m_Meshes.Add(item), item => m_Meshes.Remove(item));
            yield return new Dependency<Handle<UserVertexResource<VertexPositionNormalTexture>>>(item => m_Meshes.Add(item), item => m_Meshes.Remove(item));
        }

        public void Draw()
        {
            if (Camera == null)
                return;

            m_Device.Clear(ClearColor);

            Matrix view = Camera.GetView();

            foreach (var handle in m_Models)
            {
                Model model = handle.Get();

                //TODO use skinning api and effects (for switching textures)
                model.Draw(handle.Owner.GetWorld(), view, m_Projection);
            }

            m_MeshEffect.View = view;
            m_MeshEffect.Projection = m_Projection;

            foreach (var handle in m_Meshes)
            {
                var mesh = handle.GetAbstract<IVertexResource>();
                Entity owner = handle.Owner;

                DrawVertexBuffer(mesh, owner);
            }
        }

        private void DrawVertexBuffer(IVertexResource mesh, Entity owner)
        {
            RenderSettings settings = owner.GetComponent<RenderSettings>();
            if (settings == null)
                settings = m_DefaultRenderSettings;

            if (settings.Hidden)
                return;

            settings.Apply(m_MeshEffect);
            m_Device.BlendState = settings.BlendState;
            m_Device.RasterizerState = settings.RasterizerState;

            m_Device.SamplerStates[0] = settings.Sampler;

            m_MeshEffect.World = owner.GetWorld();

            Handle<Texture2D> texture = owner.GetComponent<Handle<Texture2D>>();
            if (texture != null)
                m_MeshEffect.Texture = texture.Get();

            mesh.PreDraw(m_Device);

            foreach (var pass in m_MeshEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                mesh.Draw(m_Device);
            }
        }
    }
}
